package com.atguigu.gmall.cart.service;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.cart.feign.GmallPmsClient;
import com.atguigu.gmall.cart.feign.GmallSmsItemClient;
import com.atguigu.gmall.cart.feign.GmallWmsClient;
import com.atguigu.gmall.cart.interceptor.LoginInterceptor;
import com.atguigu.gmall.cart.mapper.CartMapper;
import com.atguigu.gmall.cart.pojo.Cart;
import com.atguigu.gmall.cart.pojo.UserInfo;
import com.atguigu.gmall.common.bean.ResponseVo;
import com.atguigu.gmall.common.exception.CartException;
import com.atguigu.gmall.pms.entity.SkuAttrValueEntity;
import com.atguigu.gmall.pms.entity.SkuEntity;
import com.atguigu.gmall.sms.vo.ItemSaleVo;
import com.atguigu.gmall.wms.entity.WareSkuEntity;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.checkerframework.checker.units.qual.C;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import springfox.documentation.spring.web.json.Json;
import sun.rmi.runtime.Log;

import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Jia Fa
 * @create 2021-12-19 19:34
 */
@Service
public class CartService {
    @Autowired
    private GmallPmsClient pmsClient;

    @Autowired
    private GmallSmsItemClient smsClient;

    @Autowired
    private GmallWmsClient wmsClient;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private AsyncService asyncService;

    private static final String KEY_PREFIX = "cart:info:";
    private static final String PRICE_PREFIX = "cart:price:";  // 价格缓存key

    /**
     * 添加购物车方法 完成后重定向到添加商品成功页面
     *
     * @param cart
     */
    public void addCart(Cart cart) {
        // 获取登录信息
        String userId = getUserId();
        String key = KEY_PREFIX + userId; //userId 可能是userId 也可能是userKey

        // 先查询之前的购物车对象数据 获取Redis中的购物车
        BoundHashOperations<String, Object, Object> hashOps = this.redisTemplate.boundHashOps(key);

        // 判断要添加的商品是否存在购物车 进行更新数量或新增记录
        String skuId = cart.getSkuId().toString();
        BigDecimal count = cart.getCount(); // 先获取用户新添加的商品数量
        if (hashOps.hasKey(skuId)) {
            // 包含 更新数据库数量
            String cartJson = hashOps.get(skuId).toString();
            cart = JSON.parseObject(cartJson, Cart.class);
            // 更新数量
            cart.setCount(cart.getCount().add(count));

            // 异步 更新mysql数量 存在多个购物车对象 需要根据skuId 和userId 指定具体的购物车
            this.asyncService.updateCartBySkuIdAndUserId(userId,cart, skuId);

        } else {
            // 不含 新增记录  由于cart内仅传递了skuId 和 count数据,所以需要远程调用查询商品信息
            cart.setUserId(userId);
            // 根据skuId 查询商品信息
            ResponseVo<SkuEntity> skuEntityResponseVo = this.pmsClient.querySkuById(cart.getSkuId());
            SkuEntity skuEntity = skuEntityResponseVo.getData();
            if (skuEntity != null) {
                cart.setTitle(skuEntity.getTitle());
                cart.setPrice(skuEntity.getPrice());
                cart.setDefaultImage(skuEntity.getDefaultImage());
            }
            // 查询销售属性
            ResponseVo<List<SkuAttrValueEntity>> skuSaleAttrResponseVo = this.pmsClient.querySaleAttrValuesBySkuId(cart.getSkuId());
            List<SkuAttrValueEntity> skuAttrValueEntities = skuSaleAttrResponseVo.getData();
            cart.setSaleAttrs(JSON.toJSONString(skuAttrValueEntities));

            // 营销信息
            ResponseVo<List<ItemSaleVo>> smsResponseVo = this.smsClient.queryItemSaleBySkuId(cart.getSkuId());
            List<ItemSaleVo> smsResponseVoData = smsResponseVo.getData();
            cart.setSales(JSON.toJSONString(smsResponseVoData));

            // 库存信息
            ResponseVo<List<WareSkuEntity>> wareResponseVo = this.wmsClient.queryWareSkuBySid(cart.getSkuId());
            List<WareSkuEntity> wareResponseVoData = wareResponseVo.getData();
            if (!CollectionUtils.isEmpty(wareResponseVoData)) {
                cart.setStore(wareResponseVoData.stream().anyMatch(wareSkuEntity -> wareSkuEntity.getStock() - wareSkuEntity.getStockLocked() > 0));
            }
            // 商品刚加入购物车时，默认为选中状态
            cart.setCheck(true);

            // 将实时价格添加到Redis缓存中  ======用于比价======
            this.redisTemplate.opsForValue().set(PRICE_PREFIX+skuId, cart.getPrice().toString());

            // 异步保存到mysql数据库
            this.asyncService.save(userId,cart);
        }
        //同步 保存到Redis中
        hashOps.put(skuId, JSON.toJSONString(cart));
    }


    private String getUserId() {
        UserInfo userInfo = LoginInterceptor.getUserInfo();
        if (userInfo.getUserId() != null) {
            // 如果userId 不为空 说明已经登录 添加购物车以userId 为key
            return userInfo.getUserId().toString();
        }
        //否则说明未登录 以userKey为id
        return userInfo.getUserKey();
    }

    /**
     * 跳转到添加商品成功页面 需要skuId 查询Redis商品的详细信息
     * 在Redis中查到用户的购物车 将购物车中的对应的skuId商品取出来反序列化
     *
     * @param skuId
     * @return
     */
    public Cart queryCartBySkuId(Long skuId) {
        // 1.获取登录信息
        String userId = getUserId();
        String key = KEY_PREFIX + userId;
        //  查询Redis该用户的购物车对象
        BoundHashOperations<String, Object, Object> hashOps = this.redisTemplate.boundHashOps(key);
        if (hashOps.hasKey(skuId.toString())) { // 注意 Redis仅允许string类型的参数
            // 从购物车对象取出该商品
            String jsonCart = hashOps.get(skuId.toString()).toString();
            return JSON.parseObject(jsonCart, Cart.class);
        } else {
            throw new RuntimeException("您的购物车中没有该商品记录！");
        }
    }

    /**
     * 查询购物车方法
     * 根据登录状态,获取内层的map  根据skuId 获取购物车对象
     * @return
     */
    public List<Cart> queryCarts() {
        // 1 查询未登录状态的购物车  在这个方法需要区分是否登录
        UserInfo userInfo = LoginInterceptor.getUserInfo();
        String userKey = userInfo.getUserKey();

        BoundHashOperations<String, Object, Object> unLoginHashOps = this.redisTemplate.boundHashOps(KEY_PREFIX + userKey);
        // 获取map<SkuId,CartJSON> 中的CartJson集合
        List<Object> unLoginJsonCarts = unLoginHashOps.values();
        // 反序列化
        List<Cart> unLoginCarts = null;
        if (!CollectionUtils.isEmpty(unLoginJsonCarts)) {
            // 需要在未登录购物车中设置实时价格属性
            unLoginCarts = unLoginJsonCarts.stream().map(cartJson -> {
                Cart cart = JSON.parseObject(cartJson.toString(), Cart.class);
                String currentPrice = this.redisTemplate.opsForValue().get(PRICE_PREFIX + cart.getSkuId());
                // 设置实时价格
                cart.setCurrentPrice(new BigDecimal(currentPrice));
                return cart;
            }).collect(Collectors.toList());
        }
        // 2 查询登录状态
        Long userId = userInfo.getUserId();
        if (userId == null) {
            // 3 如果未登录则返回未登录购物车
            return unLoginCarts;
        }
        // 4 如果已登录将将未登录状态购物车内容合并到一登录购物车
        // 查询已登录购物车内层map
        String loginKey = KEY_PREFIX + userId;
        BoundHashOperations<String, Object, Object> loginHashOps = this.redisTemplate.boundHashOps(loginKey);

        // 4.1 先判断未登录购物车是否存在
        if (!CollectionUtils.isEmpty(unLoginCarts)) {
            // 4.2 遍历未登录购物车,将每一个商品添加或者新增到已登录购物车
            unLoginCarts.forEach(cart -> {
                BigDecimal count = cart.getCount(); // 未登录的数量
                // 判断已登录购物车是否包含该商品 查询map中的key是否存在
                if (loginHashOps.hasKey(cart.getSkuId().toString())) {
                    // 更新数量  获取登录状态的购物车并反序列化
                    String cartJson = loginHashOps.get(cart.getSkuId().toString()).toString();
                    cart = JSON.parseObject(cartJson, Cart.class);
                    cart.setCount(cart.getCount().add(count));
                    //同步更新Redis 异步更新mysql
                    this.asyncService.updateCartBySkuIdAndUserId(userId.toString(),cart, cart.getSkuId().toString());

                } else {
                    // 新增记录 将未登录购物车中的key替换为已登录的key直接保存即可
                    cart.setUserId(userId.toString());
                    // 清空主键
                    cart.setId(null);
                    this.asyncService.save(userId.toString(),cart);
                }
                // 更新或者新增Redis  loginHashOps = <SkuId,CartJson>
                loginHashOps.put(cart.getSkuId().toString(), JSON.toJSONString(cart));
            });
            // 5 只有存在未登录购物车才需要清空未登录购物车
            // 5.1 同步清空Redis未登录数据  此处在遍历外层,应当删除整个购物车
            this.redisTemplate.delete(KEY_PREFIX + userKey);
            // 5.2 异步清空mysql未登录数据
            this.asyncService.delete(userKey);
        }

        // 6 查询已登录购物车内容,返回
        List<Object> loginCartJson = loginHashOps.values();
        if (!CollectionUtils.isEmpty(loginCartJson)){
            //同未登录购物车一致 设置实时价格
            return loginCartJson.stream().map(cartJson->{
                Cart cart = JSON.parseObject(cartJson.toString(), Cart.class);
                // 查询缓存中的实时价格
                String currentPrice = this.redisTemplate.opsForValue().get(PRICE_PREFIX + cart.getSkuId());
                // 设置实时价格
                cart.setCurrentPrice(new BigDecimal(currentPrice));
                return cart;
            }).collect(Collectors.toList());
        }
        return null;
    }

    /**
     * 修改商品数量  (点击+1 与-1 与直接修改数量)
     * @param cart
     */
    public void updateNum(Cart cart) {
        // 获取登录信息  获取该用户购物车map  此时不论是否登录都可以进行修改
        String userId = this.getUserId();
        String key = KEY_PREFIX + userId;
        BoundHashOperations<String, Object, Object> HashOps = this.redisTemplate.boundHashOps(key);

        // 判断该用户购物车是否包含该商品
        if (HashOps.hasKey(cart.getSkuId().toString())){
            // 包含商品 更新数量
            BigDecimal count = cart.getCount(); // 页面传递最新的数量
            String cartJson = HashOps.get(cart.getSkuId().toString()).toString();
            cart = JSON.parseObject(cartJson, Cart.class);
            cart.setCount(count);
            // 同步更新该商品的 Redis 异步更新mysql
            HashOps.put(cart.getSkuId().toString(), JSON.toJSONString(cart));
            this.asyncService.updateCartBySkuIdAndUserId(userId,cart, cart.getSkuId().toString());
        }

    }

    /**
     * 删除购物车
     * @param skuId
     */
    public void deleteCart(Long skuId) {
        // 获取该用户的购物车信息 不区分是否登录
        String userId = this.getUserId();
        // 通过外层key获取内层的map
        BoundHashOperations<String, Object, Object> hashOps = this.redisTemplate.boundHashOps(KEY_PREFIX + userId);
        // 判断该用户的购物车是否包含该信息 其实不需要判断 直接删除即可
        if (hashOps.hasKey(skuId.toString())){
            hashOps.delete(skuId.toString());
            this.asyncService.deleteByUserIdAndSkuId(userId,skuId);
        }
    }

    /**
     * 注意 由于是远程Feign访问 不能从拦截器中获取userId 所以需要传递过来userId 查询
     * @param userId
     * @return
     */
    public List<Cart> queryCheckCartByUserId(Long userId) {
        String key = KEY_PREFIX+userId;

        BoundHashOperations<String, Object, Object> hashOps = this.redisTemplate.boundHashOps(key);
        List<Object> cartJsons = hashOps.values();

        if (CollectionUtils.isEmpty(cartJsons)){
            //为空 异常
            throw new CartException("该用户的购物车不存在");
        }
        // 获取所有的购物车集合 过滤其中选中的购物车filter(Cart::getCheck) check=true的
        List<Cart> carts = cartJsons.stream().map(cartJson -> JSON.parseObject(cartJson.toString(), Cart.class))
                .filter(Cart::getCheck).collect(Collectors.toList());
        return carts;
    }
}
